/*
 * Project: Sudoku Explainer
 * Copyright (C) 2006-2007 Nicolas Juillerat
 * 
 * Project: Sudoku Hint Engine for Android
 * Copyright (C) 2010-2013 Emanuel Moecklin
 * 
 * Available under the terms of the Lesser General Public License (LGPL)
 */
package com.onegravity.sudoku.solver.rules.wing;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.net.Uri;

import com.onegravity.sudoku.solver.HintResource;
import com.onegravity.sudoku.solver.HintType;
import com.onegravity.sudoku.solver.R;
import com.onegravity.sudoku.solver.core.HasParentPotentialHint;
import com.onegravity.sudoku.solver.core.IndirectHint;
import com.onegravity.sudoku.solver.core.Potential;
import com.onegravity.sudoku.solver.core.Rule;
import com.onegravity.sudoku.solver.data.Cell;
import com.onegravity.sudoku.solver.data.Grid;
import com.onegravity.sudoku.solver.data.ImmutableCell;
import com.onegravity.sudoku.solver.util.BitSetFactory;
import com.onegravity.sudoku.solver.util.BitSetIterator;
import com.onegravity.sudoku.solver.util.HtmlLoader;
import com.onegravity.sudoku.solver.util.Link;

/**
 * Wing hints (XY, XYZ, WXYZ, ..., RSTUVWXYZ)
 * @author Emanuel Moecklin
 */
public class WingHint extends IndirectHint implements Rule, HasParentPotentialHint {

	private static final long serialVersionUID = 6877263990019256831L;
	
    private final int degree;
    private final Cell pivotCell;
    private final List<Cell> pincerCells;
    private final List<Cell> allCells;
    private final int zValue;

	private final boolean hasExtraRegions;
	private final boolean isJigsaw;

	public WingHint(Map<Cell,BitSet> removablePotentials, int degree, Cell pivotCell, List<Cell> pincerCells, int zValue, boolean hasExtraRegions, boolean isJigsaw) {
        super(removablePotentials);
        this.degree = degree;
        this.pivotCell = pivotCell;
        this.pincerCells = pincerCells;
        this.zValue = zValue;
        this.hasExtraRegions = hasExtraRegions;
        this.isJigsaw = isJigsaw;
        allCells = new ArrayList<Cell>(pincerCells);
        allCells.add(pivotCell);
    }

    @Override
    public Collection<Cell> getSelectedCells(HintType hintType) {
    	switch (hintType) {
		case SMALL_CLUE: return allCells;
		case BIG_CLUE:
		case HINT:
		default:
			return Collections.singletonList(pivotCell);
    	}
    }

    @Override
    public Map<Cell, BitSet> getSelectedPotentials(HintType hintType, int viewNum) {
    	if (hintType!=HintType.HINT) return null;
    	
        Map<Cell, BitSet> result = new HashMap<Cell, BitSet>();
        BitSet zSet = BitSetFactory.createOneElement(zValue);
        for (Cell pincerCell:pincerCells) {
            result.put(pincerCell, zSet);
        }
        if (pivotCell.hasPotentialValue(zValue)) result.put(pivotCell, zSet);
        return result;
    }

    @Override
    public Map<Cell, BitSet> getAddedPotentials(HintType hintType, int viewNum) {
    	if (hintType!=HintType.HINT) return null;
        Map<Cell, BitSet> result = new HashMap<Cell, BitSet>();
        result.put(pivotCell, removeZValue(pivotCell));
        return result;
    }

    @Override
    public Map<Cell, BitSet> getSecondarySelectedPotentials(HintType hintType, int viewNum) {
    	if (hintType==HintType.HINT) {
            Map<Cell, BitSet> result = new HashMap<Cell, BitSet>();
            for (Cell pincerCell:pincerCells) {
                result.put(pincerCell, removeZValue(pincerCell));
            }
            return result;
    	}
    	else if (hintType==HintType.BIG_CLUE) {
            Map<Cell, BitSet> result = new HashMap<Cell, BitSet>();
            for (Cell pincerCell:pincerCells) {
                result.put(pincerCell, pincerCell.getPotentialValues());
            }
            result.put(pivotCell, pivotCell.getPotentialValues());
            return result;
    	}
    	return null;
    }

    @Override
    public Map<ImmutableCell, BitSet> getRemovedPotentials(HintType hintType) {
        return (hintType==HintType.HINT) ? super.getRemovedPotentials(hintType) : null;
    }

    @Override
    public Collection<Link> getLinks(HintType type, int viewNum) {
        Collection<Link> result = new ArrayList<Link>();
        for (Cell pincerCell:pincerCells) {
            int value = getRemainingValue(pincerCell);
            Link link = new Link(pivotCell, value, pincerCell, value);
            result.add(link);
        }
        return (type==HintType.HINT) ? result : null;
    }

    @Override
    public double getDifficulty() {
    	double addDiff = (hasExtraRegions ? 0.2 : 0.0) + (isJigsaw ? 0.2 : 0.0);
    	switch (degree) {
    	default:
    	case 2: return 4.2 + addDiff;
    	case 3: return 4.4 + addDiff;
    	case 4: return 4.6 + addDiff;
    	case 5: return 4.8 + addDiff;
    	case 6: return 5.0 + addDiff;
    	case 7: return 5.2 + addDiff;
    	case 8: return 5.4 + addDiff;
    	case 9: return 5.6 + addDiff;
    	}
    }

    @Override
    public String getName() {
    	return getHintResource().getName();
    }

    @Override
    public Uri getLink() {
    	return getHintResource().getUrl();
	}

    public Collection<Potential> getRuleParents(Grid initialGrid, Grid currentGrid) {
        Collection<Potential> result = new ArrayList<Potential>();
        for (Cell cell:allCells) {
            Cell tmpCell = initialGrid.getCell(cell.getX(), cell.getY());
            for (int p = 1; p <= 9; p++) {
                if (tmpCell.hasPotentialValue(p) && !cell.hasPotentialValue(p)) {
                    result.add(new Potential(cell, p, false));
                }
            }
        }
        return result;
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof WingHint)) return false;
        WingHint other = (WingHint)o;
        if (this.degree != other.degree) return false;
        if (this.zValue != other.zValue) return false;
        if (this.hasExtraRegions != other.hasExtraRegions) return false;
        if (this.isJigsaw != other.isJigsaw) return false;
        if (this.allCells.size() != other.allCells.size()) return false;
        for (int i=0; i<this.allCells.size(); i++) {
        	if (!this.allCells.get(i).equals(other.allCells.get(i))) return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
    	int hash = 0;
    	for (Cell cell:allCells) {
    		hash ^= cell.hashCode();
    	}
        return hash;
    }

    @Override
    public String getHintHtml(HintType hintType) {
    	HintResource hintRes = getHintResource();
        String result = HtmlLoader.loadHtml(hintRes.hint(hintType));
        
        // create chain of conclusions: if its value is {digit} then cell {pincer} must be {zValue}.
        StringBuffer reasoning = new StringBuffer();
        BitSet candidates = pivotCell.getPotentialValues();
        for (BitSetIterator<Integer> it = new BitSetIterator<Integer>(candidates); it.hasNext(); ) {
        	int candidate = it.next();
        	if (candidate==zValue) continue;
        	// find pincer cell & additional candidate
        	Cell pincerCell = null;
        	for (Cell cell:pincerCells) {
        		if (cell.hasPotentialValue(candidate)) {
        			pincerCell = cell;
        			break;
        		}
        	}
            String reason = HintResource.getString(R.string.hint_wing_reasoning, candidate, pincerCell.toString(), zValue);
            reasoning.append(reason);
        }

        result = HtmlLoader.format(result, getName(), pivotCell.toString(), zValue, pivotCell.getPotentialValues().cardinality(),
        						   Cell.toString(allCells.toArray(new Cell[allCells.size()])),
        						   Cell.toString(pincerCells.toArray(new Cell[pincerCells.size()])),
        						   reasoning);
        return result;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(getName());
        builder.append(": "); //$NON-NLS-1$
        builder.append(Cell.toFullString(allCells.toArray(new Cell[allCells.size()])));
        builder.append(HintResource.getString(R.string.hint_wing_onvalue));
        builder.append(zValue);
        return builder.toString();
    }

    @Override
    public int getNrOfSteps() {
        return 3;
    }

    private int getRemainingValue(Cell cell) {
        BitSet result = removeZValue(cell);
        return result.nextSetBit(0);
    }
    
    private BitSet removeZValue(Cell cell) {
    	BitSet result = (BitSet) cell.getPotentialValues().clone();
    	result.clear(zValue);
    	return result;
    }
    
    private HintResource getHintResource() {
    	switch (degree) {
    	default:
    	case 2: return HintResource.XYWING;
    	case 3: return HintResource.XYZWING;
    	case 4: return HintResource.WXYZWING;
    	case 5: return HintResource.VWXYZ_WING;
    	case 6: return HintResource.UVWXYZ_WING;
    	case 7: return HintResource.TUVWXYZ_WING;
    	case 8: return HintResource.STUVWXYZ_WING;
    	case 9: return HintResource.RSTUVWXYZ_WING;
    	}
    }

}